/// Copyright (c) Microsoft Corporation.  All rights reserved.
using System;
using System.Diagnostics;
using Microsoft.Build.Framework;
using Microsoft.Build.Utilities;

namespace TfsTasks
{
    public class ExecMulti : Task
    {
        #region Constants
        private const int MAXIMUM_PROCESS_EXIT_WAIT_MILLISECONDS = 1800000; // 30 minutes. TODO: Expose as property.
        private const int FAILURE_EXIT_CODE = 101;
        private const string MESSAGE_COMMAND_EXECUTED_SUCCESSFULLY = "The command template has completed successfully.";
        private const string MESSAGE_COMMAND_CURRENTLY_EXECUTED = "Current command: {0} {1}.";
        private const string MESSAGE_FAILED_INVALID_COMMAND = "Invalid or empty command.";
        private const string MESSAGE_FAILED_INVALID_ARGUMENTS_TEMPLATE = "Invalid or empty arguments template.";
        private const string MESSAGE_FAILED_INVALID_PARAMETERS_LIST = "Invalid or empty paramters list.";
        private const string MESSAGE_FAILED_INVALID_DELIMITER = "Invalid or empty delimiter.";
        private const string MESSAGE_FAILED_RUNNING_COMMAND_PROCESS = "Result: {0}. Failed running command [Exit Code: {1}]: {2} {3}.";
        private const string MESSAGE_FAILED_COMMAND_TIMEOUT = "Timeout expired waiting for process to exit (waited {0} milliseconds).";
        #endregion

        #region Private Fields
        private string _command;
        private string _argumentsTemplate;
        private string _parametersList;
        private string _delimiter;
        private string _workingDirectory;
        private int _exitCode;
        private bool _ignoreExitCode;
        #endregion

        #region Public Properties
        // The [Required] attribute indicates a required property.
        // If a project file invokes this task without passing a value
        // to this property, the build will fail immediately.
        [Required]
        public string ArgumentsTemplate
        {
            get
            {
                return _argumentsTemplate;
            }
            set
            {
                _argumentsTemplate = value;
            }
        }

        [Required]
        public string ParametersList
        {
            get
            {
                return _parametersList;
            }
            set
            {
                _parametersList = value;
            }
        }

        [Required]
        public string Delimiter
        {
            get
            {
                return _delimiter;
            }
            set
            {
                _delimiter = value;
            }
        }

        [Required]
        public string Command
        {
            get
            {
                return _command;
            }
            set
            {
                _command = value;
            }
        }

        [Required]
        public string WorkingDirectory
        {
            get
            {
                return _workingDirectory;
            }
            set
            {
                _workingDirectory = value;
            }
        }

        [Required]
        public bool IgnoreExitCode
        {
            get
            {
                return _ignoreExitCode;
            }
            set
            {
                _ignoreExitCode = value;
            }
        }

        [Output]
        public int ExitCode
        {
            get
            {
                return _exitCode;
            }
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Execute the task.
        /// </summary>
        /// <returns>true upon success and false upon a failure.</returns>
        public override bool Execute()
        {
            return this.ExecuteInternal();
        }
        #endregion

        #region Private Methods
        private bool ExecuteInternal()
        {
            if (string.IsNullOrEmpty(_command))
            {
                Log.LogMessage(MessageImportance.High, MESSAGE_FAILED_INVALID_COMMAND);
                _exitCode = FAILURE_EXIT_CODE;
                return (_ignoreExitCode);
            }

            if (string.IsNullOrEmpty(_argumentsTemplate))
            {
                Log.LogMessage(MessageImportance.High, MESSAGE_FAILED_INVALID_ARGUMENTS_TEMPLATE);
                _exitCode = FAILURE_EXIT_CODE;
                return (_ignoreExitCode);
            }

            if (string.IsNullOrEmpty(_parametersList))
            {
                Log.LogMessage(MessageImportance.High, MESSAGE_FAILED_INVALID_PARAMETERS_LIST);
                _exitCode = FAILURE_EXIT_CODE;
                return (_ignoreExitCode);
            }

            if (string.IsNullOrEmpty(_delimiter))
            {
                Log.LogMessage(MessageImportance.High, MESSAGE_FAILED_INVALID_DELIMITER);
                _exitCode = FAILURE_EXIT_CODE;
                return (_ignoreExitCode);
            }

            try
            {
                ProcessStartInfo processInfo = new ProcessStartInfo();
                processInfo.CreateNoWindow = true;
                processInfo.UseShellExecute = false;
                processInfo.WindowStyle = ProcessWindowStyle.Hidden;
                processInfo.WorkingDirectory = _workingDirectory;
                processInfo.RedirectStandardError = true;
                processInfo.RedirectStandardOutput = true;
                processInfo.FileName = _command;
                string[] parameters = _parametersList.Split(new string[]{_delimiter}, StringSplitOptions.RemoveEmptyEntries);
                for (int i = 0; i < parameters.Length; i++)
                {
                    string templateReplacementValue = string.Format("\"{0}\"", parameters[i]);
                    string arguments = string.Format(_argumentsTemplate, templateReplacementValue);
                    Log.LogMessage(MessageImportance.High, MESSAGE_COMMAND_CURRENTLY_EXECUTED, _command, arguments);
                    processInfo.Arguments = arguments;
                    using (Process process = new Process())
                    {
                        process.StartInfo = processInfo;

                        // Damon's fix:
                        ProcessOutput processOutput = new ProcessOutput();

                        process.OutputDataReceived += processOutput.OutputDataReceived;
                        process.ErrorDataReceived += processOutput.ErrorDataReceived;

                        bool result = process.Start();

                        process.BeginOutputReadLine();
                        process.BeginErrorReadLine();

                        process.WaitForExit(MAXIMUM_PROCESS_EXIT_WAIT_MILLISECONDS);

                        // Read what is already there in the standard output and standard error streams.
                        Log.LogMessage(MessageImportance.High, processOutput.StandardOutput);
                        Log.LogMessage(MessageImportance.High, processOutput.StandardError);

                        if (process.HasExited)
                            _exitCode = process.ExitCode;
                        else
                        {
                            process.Kill();
                            Log.LogMessage(MessageImportance.High,
                                MESSAGE_FAILED_COMMAND_TIMEOUT,
                                MAXIMUM_PROCESS_EXIT_WAIT_MILLISECONDS);
                            _exitCode = FAILURE_EXIT_CODE;
                        }

                        if (!result || _exitCode != 0)
                        {
                            Log.LogMessage(MessageImportance.High, 
                                MESSAGE_FAILED_RUNNING_COMMAND_PROCESS,
                                result.ToString(), _exitCode, _command, arguments);
                            _exitCode = FAILURE_EXIT_CODE;

                            return (_ignoreExitCode);
                        }
                    }
                }
            }
            catch (Exception ex)
            {
                Log.LogErrorFromException(ex, true);
                _exitCode = FAILURE_EXIT_CODE;
                return (_ignoreExitCode);
            }

            Log.LogMessage(MessageImportance.High, MESSAGE_COMMAND_EXECUTED_SUCCESSFULLY);
            return true;
        }
        #endregion
    }
}
